﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Net;
using System.IO;
using System.Threading;
using BuildBox.Core.Logging;
using BuildBox.Core.Extensions;
using BuildBox.Core.Server.Adapters;
using BuildBox.Core.Server;
using BuildBox.Core.Server.Requests;

namespace BuildBox.Core.Server
{
    public class HttpServer : IHttpServer
    {
        private readonly ILog _log;
        private readonly IConfiguration _configuration;
        private readonly SemaphoreSlim _concurrentRequest;
        private readonly Func<IHttpListenerAdapter> _listenerFactory;
        private readonly IList<Request> _requestCollection;
        private readonly Dictionary<Type, IRequestHandler> _activeHandlers;

        private IHttpListenerAdapter _listener;

        public HttpServer(IConfiguration configuration, ILog log, Func<IHttpListenerAdapter> listenerFactory, IList<Request> requestCollection)
        {
            _configuration = configuration;
            _log = log;
            _listenerFactory = listenerFactory;
            _requestCollection = requestCollection;
            _activeHandlers = new Dictionary<Type, IRequestHandler>();
            _concurrentRequest = new SemaphoreSlim(128);
        }

        public void Start()
        {
            _listener = _listenerFactory();
            string virtualDir = _configuration.VirtualDirectory + (_configuration.VirtualDirectory.EndsWith("/") ? string.Empty : "/");
            string listenOn = "http://+:{0}{1}".FormatWith(_configuration.Port, virtualDir);
            _listener.AddPrefix(listenOn);
            _listener.Start();
            _listener.BeginGetContext(ListenerCallback, null);
            _log.Info("Starting HTTP Server at " + listenOn);
        }

        public void Stop()
        {
            if (_listener != null)
            {
                _log.Info("Stopping HTTP Server");
                _listener.Stop();
                _listener = null;
            }
        }

        public void Dispose()
        {
            Stop();
        }

        private void ListenerCallback(IAsyncResult result)
        {
             IHttpListenerContextAdapter context = _listener.EndGetContext(result, _configuration);

            _listener.BeginGetContext(ListenerCallback, null);
            _log.Info("Processing Request [{0}] {1}".FormatWith(context.Request.HttpMethod, context.Request.Url));

            if (!_concurrentRequest.Wait(TimeSpan.FromSeconds(15)))
            {
                context.Response.ServerBusy();
                return;                
            }

            try
            {
                ProcessRequest(context);
            }
            finally
            {
                _concurrentRequest.Release();
            }
        }

        private void ProcessRequest(IHttpListenerContextAdapter context)
        {
            foreach (Request requestMap in _requestCollection)
            {
                if (!requestMap.CanHandle(context.Request))
                {
                    continue;
                }

                IRequestHandler handler;
                if (_activeHandlers.ContainsKey(requestMap.Type))
                {
                    handler = _activeHandlers[requestMap.Type];
                }
                else
                {
                    handler = Activator.CreateInstance(requestMap.Type) as IRequestHandler;
                    _activeHandlers.Add(requestMap.Type, handler);
                }

                if (handler == null)
                {
                    continue;
                }

                handler.Respond(context);
                return;
            }

            // Request was not found
            context.Response.SetNotFoundStatus();            
        }
    }
}
